﻿using System.Text.Json;
using System.Text.Json.Nodes;
using SMCL.Core.Utils.Static;
using SMCL.Core.Utils.Types;

namespace SMCL.Core.Utils.Game
{
    /// <summary>
    /// Download.xaml 的交互逻辑
    /// </summary>
    public partial class Download
    {
        private readonly Uri _gameInfoUrl;
        private static readonly int _threads = 4;
        public async Task StartDownload(Action<string> setDownloading)
        {
            try
            {
                var client = new HttpClient();
                client.DefaultRequestHeaders.UserAgent.Add(new(Launcher.Brand, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString()));
                client.DefaultRequestHeaders.UserAgent.Add(new("Microsoft.NET", Environment.Version.ToString()));
                client.Timeout = new(0, 0, 30);
                setDownloading(_gameInfoUrl.ToString());
                var gameInfoResponse = await client.GetAsync(_gameInfoUrl);
                var gameInfo = new GameInfo(JsonSerializer.Deserialize<Types.Json.GameInfo>((await gameInfoResponse.Content.ReadAsStringAsync()).Replace("piston-data.mojang.com", "bmclapi2.bangbang93.com").Replace("libraries.minecraft.net", "bmclapi2.bangbang93.com/maven")), _gameInfoUrl.Segments.Last()[..^5]);
                setDownloading(gameInfo.AssetIndex.Url.ToString());
                var assetIndexResponse = await client.GetAsync(gameInfo.AssetIndex.Url);
                var assetIndexString = await assetIndexResponse.Content.ReadAsStringAsync();
                var assetIndex = new AssetIndex(JsonNode.Parse(assetIndexString.Replace("resources.download.minecraft.net", "bmclapi2.bangbang93.com/assets")));
                if (!Directory.Exists(".minecraft"))
                    Directory.CreateDirectory(".minecraft");
                if (!Directory.Exists(".minecraft\\versions"))
                    Directory.CreateDirectory(".minecraft\\versions");
                var gameVersion = _gameInfoUrl.Segments.Last()[..^5];
                if (!Directory.Exists($".minecraft\\versions\\{gameVersion}"))
                    Directory.CreateDirectory($".minecraft\\versions\\{gameVersion}");
                var gameName = gameInfo.Downloads.Client.Url.Segments.Last();
                setDownloading(gameName);
            retryDownload:
                try
                {
                    await File.WriteAllBytesAsync($".minecraft\\versions\\{gameVersion}\\{gameName}", await (await client.GetAsync(gameInfo.Downloads.Client.Url)).Content.ReadAsByteArrayAsync());
                }
                catch
                {
                    goto retryDownload;
                }
                if (!Directory.Exists($".minecraft\\versions\\{gameVersion}\\native"))
                    Directory.CreateDirectory($".minecraft\\versions\\{gameVersion}\\native");
                var downloadTasks = Array.ConvertAll(gameInfo.Libraries, (lib) => DownloadTask.FromLibrary(lib, gameVersion)).ToList();
                var tasks = new Task[_threads];
                for (int i = 0; i < _threads; i++)
                    tasks[i] = RunDownloadTask(downloadTasks, client, setDownloading);
                await Task.Run(() => Task.WaitAll(tasks));
                downloadTasks = [.. Array.ConvertAll(assetIndex.Objects, DownloadTask.FromObject)];
                for (int i = 0; i < _threads; i++)
                    tasks[i] = RunDownloadTask(downloadTasks, client, setDownloading);
                await Task.Run(() => Task.WaitAll(tasks));
                if (!Directory.Exists(".minecraft/assets/virtual/legacy/"))
                    CopyDirectory(".minecraft/assets/objects/", ".minecraft/assets/virtual/legacy/", true);
                var launchInfo = new LaunchGameInfo(gameInfo);
                await File.WriteAllTextAsync($".minecraft\\versions\\{gameVersion}\\SmclSettings.json", JsonSerializer.Serialize(launchInfo));
                if (!Directory.Exists(".minecraft\\assets\\indexes"))
                    Directory.CreateDirectory(".minecraft\\assets\\indexes");
                await File.WriteAllTextAsync($".minecraft\\assets\\indexes\\{gameInfo.AssetIndex.Url.Segments.Last()}", assetIndexString);
            }
            catch (Exception ex)
            {
                throw new DownloadFailedException($"下载失败！参见内部异常", ex);
            }
        }
        async Task RunDownloadTask(List<DownloadTask> tasks, HttpClient client, Action<string> setDownloading)
        {
            while (true)
            {
                DownloadTask task;
                lock (tasks)
                {
                    if (tasks.Count == 0)
                        return;
                    task = tasks.Last();
                    tasks.Remove(task);
                }
                setDownloading(task.DisplayName);
            retry:
                try
                {
                    var fi = new FileInfo(task.Path);
                    if (!Directory.Exists(fi.Directory.FullName))
                        Directory.CreateDirectory(fi.Directory.FullName);
                    if (!fi.Exists)
                        await File.WriteAllBytesAsync(task.Path, await (await client.GetAsync(task.Url)).Content.ReadAsByteArrayAsync());
                }
                catch
                {
                    goto retry;
                }
            }
        }
        static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
        {
            // Get information about the source directory
            var dir = new DirectoryInfo(sourceDir);

            // Check if the source directory exists
            if (!dir.Exists)
                throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");

            // Cache directories before we start copying
            DirectoryInfo[] dirs = dir.GetDirectories();

            // Create the destination directory
            Directory.CreateDirectory(destinationDir);

            // Get the files in the source directory and copy to the destination directory
            foreach (FileInfo file in dir.GetFiles())
            {
                string targetFilePath = Path.Combine(destinationDir, file.Name);
                file.CopyTo(targetFilePath);
            }

            // If recursive and copying subdirectories, recursively call this method
            if (recursive)
            {
                foreach (DirectoryInfo subDir in dirs)
                {
                    string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
                    CopyDirectory(subDir.FullName, newDestinationDir, true);
                }
            }
        }
    }
}
